Skip to content

fix(agent): 明确 pending 消息消费决策#810

Merged
dingyi222666 merged 7 commits intoChatLunaLab:v1-devfrom
CookSleep:fix/pending-message-decision
Apr 5, 2026
Merged

fix(agent): 明确 pending 消息消费决策#810
dingyi222666 merged 7 commits intoChatLunaLab:v1-devfrom
CookSleep:fix/pending-message-decision

Conversation

@CookSleep
Copy link
Copy Markdown
Member

Summary

  • 将 agent 的 round-decision 语义从 canContinue 明确为 willConsumePendingMessages,让 pending message 消费决策更直观
  • character_reply 最终完成分支补充 reply-emitted 标记与 pending drain,避免回复已由工具发送时 trace 仍表现为空输出
  • 同步主聊天服务对新事件字段的消费逻辑

Verification

  • yarn fast-build core

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 06e3e9a7-1227-4c77-85f7-490404444aeb

📥 Commits

Reviewing files that changed from the base of the PR and between 14756cb and 2e6764c.

📒 Files selected for processing (7)
  • packages/core/src/middlewares/chat/rollback_chat.ts
  • packages/core/src/middlewares/chat/stop_chat.ts
  • packages/core/src/services/conversation.ts
  • packages/core/src/services/conversation_runtime.ts
  • packages/core/src/utils/conversation.ts
  • packages/extension-agent/client/components/mcp/mcp-servers-view.vue
  • packages/extension-tools/src/plugins/group.ts

Walkthrough

更新代理执行器以更精准处理工具的直接输出(含 replyEmitted),扩展 AgentEvent.done,在平台层新增可注册的工具掩码解析器并外放类型,重构会话运行时的请求索引与锁逻辑,并同步调整中间件、服务、适配器与相关测试以匹配接口变更。

Changes

Cohort / File(s) Summary
代理执行器
packages/core/src/llm-core/agent/legacy-executor.ts
移除旧的 pre-tool continuation 推断;在 post-tool 阶段若工具为直接输出(returnDirectisDirectToolOutput),发出 round-decision(canContinue:false)、排空并发 human-update 并基于 replyEmitted 发出 done(空输出或 toOutput(last.observation));否则发出 round-decision(canContinue:true) 再递增迭代。
代理类型与子代理事件处理
packages/core/src/llm-core/agent/types.ts, packages/core/src/llm-core/agent/sub-agent.ts
AgentEvent'done' 增加可选 replyEmitted?: boolean 字段;sub-agent 在处理 done 时若 replyEmitted 为真,最终输出追踪文本改为固定提示字符串。
平台工具掩码解析(新增可注册解析器)
packages/core/src/llm-core/platform/service.ts, packages/core/src/services/types.ts
在 PlatformService 中新增 _toolMaskResolvers、导出 ToolMaskArg/ToolMaskResolver 类型、registerToolMaskResolverresolveToolMask API;上层 service types 改为从该模块 re-export。
聊天服务对接改动
packages/core/src/services/chat.ts, packages/extension-agent/src/service/permissions.ts
移除本地 _toolMaskResolvers 管理;注册/解析委托到平台服务(this._platformService / ctx.chatluna.platform),并调整相应导入类型来源。
会话运行时与请求管理重构
packages/core/src/services/conversation_runtime.ts, packages/core/tests/conversation-runtime.spec.ts, packages/core/src/services/conversation.ts, packages/core/src/services/types.ts
删除跨平台/会话多索引,改为按 conversationId 管理 activeByConversationActiveRequest 增加 platformrequestKey);registerRequest 增加 platform 参数,getRequestId 改为需传入 conversationIdcompleteRequestdispose 行为/签名调整;引入 runLock 并统一锁清理逻辑;更新相关测试断言与调用。
中间件与请求 ID 处理
packages/core/src/middlewares/chat/stop_chat.ts, packages/core/src/middlewares/conversation/request_conversation.ts
stop_chat 改为调用基于 conversation 的停止接口(stopConversationRequest(conversation.id));请求中间件使用 context.options.messageId 作为传入的 requestId(不再自行构造 requestId)。
消息/内容处理小调整
packages/core/src/services/message_transform.ts
文件内对若干接口/类型做了位置重排(MessageTransformOptions 等),导出签名未变。
适配器与共享客户端更改
packages/adapter-gemini/src/utils.ts, packages/shared-adapter/src/client.ts
processImageParts 的模型 vision 能力匹配从 gemma 改为 gemma2;shared-adapter 为 supportImageInput 增加 gemma 匹配,新增 gemini-3.1-pro 的上下文大小映射并更改默认回退值。
UI 与表单规范化
packages/extension-agent/client/components/mcp/mcp-servers-view.vue
增加 stdio 命令规范化(split/normalize),JSON 模式下添加解析错误指示与校验函数,保存流程统一通过 validateServer 严格校验并使用规范化后的配置。
小幅导入/样式或格式变更
packages/extension-agent/src/service/sub_agent.ts, packages/core/src/middlewares/chat/rollback_chat.ts, packages/core/src/utils/conversation.ts, packages/extension-tools/src/plugins/group.ts
若干导入项重排、类型 import 精简与 Zod schema 行格式化调整(无逻辑变更)。

Sequence Diagram(s)

sequenceDiagram
    participant AgentExec as AgentExecutor
    participant ToolRT as ToolRuntime
    participant HumanQ as HumanUpdateQueue
    participant Events as EventEmitter

    AgentExec->>ToolRT: execute(action)
    ToolRT-->>AgentExec: observation (可能含 replyEmitted)
    alt observation 为直接输出 (returnDirect 或 isDirectToolOutput)
        AgentExec->>Events: emit round-decision(canContinue:false)
        AgentExec->>HumanQ: drain pending human-update -> emit human-update events
        alt observation.replyEmitted == true
            AgentExec->>Events: emit done { output: "", replyEmitted:true }
        else
            AgentExec->>Events: emit done { output: toOutput(observation), replyEmitted:false }
        end
    else 常规工具输出
        AgentExec->>Events: emit round-decision(canContinue:true)
        AgentExec->>AgentExec: iterations++
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 分钟

可能相关的 PRs

诗歌

🐰 工具跳跃声轻盈,轮次因它短又停,
若是回复已被发出,我便悄悄数星星。
平台织成新花桥,掩码有名可寻觅,
小兔鼓掌贺重构,代码花开又一季。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确反映了本次更改的核心内容,即明确 agent pending 消息的消费决策逻辑。
Description check ✅ Passed 描述清晰地解释了三个主要改动:round-decision 语义调整、character_reply 补充标记、以及主聊天服务的同步更新。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CookSleep CookSleep force-pushed the fix/pending-message-decision branch from 959b58e to 70814bb Compare April 3, 2026 05:29
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request renames the canContinue property to willConsumePendingMessages across the agent execution and chat service logic to more accurately describe its function. It also introduces a mechanism to handle final character replies emitted by tools, adding a replyEmitted flag to the done event and updating the UI to reflect when a reply has been sent by a tool. Furthermore, the Docker configuration guide for the extension agent has been updated with recommendations for setting home and working directories, and the parameter descriptions for the group mute tool have been refined for better clarity. I have no feedback to provide as there were no review comments to evaluate.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/llm-core/agent/legacy-executor.ts (1)

317-335: ⚠️ Potential issue | 🔴 Critical

直接工具输出分支在协化阶段后无法触发,Line 333 的输出也会被重新序列化。

Line 317 的检查 isDirectToolOutput(last.observation) 无法命中,因为 last.observation 在 Line 93-101 的 coerceToAgentObservation() 中已经被处理。该函数的 isAgentObservation() 内部判断仅接受字符串或 MessageContentComplex 数组;DirectToolOutput 对象(作为普通对象)都不符合这些条件,因此被 JSON.stringify() 转换成字符串。到达 Line 317 时,观察值已是字符串,isDirectToolOutput() 返回 false,分支不可达。

即使分支能被触发,Line 333 的 toOutput() 仍会对这个值走字符串化路径,而不是提取真正的直接工具输出内容,导致基于 DirectToolOutput 的终止逻辑失效。

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/llm-core/agent/types.ts`:
- Around line 197-200: AgentObservation now includes DirectToolOutput but
consumers assume string/array; add type guards to handle DirectToolOutput
explicitly: where code reads the observation variable (e.g., the length/null
checks and string interpolation using observation and observationPrefix, and
places that feed ToolMessage content), first call
isDirectToolOutput(observation) (or inline check for the DirectToolOutput shape)
and either convert it to a string/appropriate ToolMessage content via the same
serialization used by coerceToAgentObservation or branch to handle its fields,
ensuring you don't access observation.length or interpolate the object directly;
update both the observation length/null check site and the buffer +
`\n${observationPrefix}${observation}\n` concatenation to guard/transform
DirectToolOutput before use.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b0300d7d-6b2e-421e-aa7e-750664297aee

📥 Commits

Reviewing files that changed from the base of the PR and between 086a1f7 and 0cc2c11.

📒 Files selected for processing (4)
  • packages/core/src/llm-core/agent/legacy-executor.ts
  • packages/core/src/llm-core/agent/sub-agent.ts
  • packages/core/src/llm-core/agent/types.ts
  • packages/core/src/services/chat.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/adapter-gemini/src/utils.ts (1)

211-220: ⚠️ Potential issue | 🟠 Major

修复 gemma 模型的图片处理逻辑不一致导致的数据丢弃问题

packages/shared-adapter/src/client.ts 第 152 行的 imageModelMatchers 包含 'gemma' 模式,导致 supportImageInput('gemma-7b') 返回 true,上游代码会为这些模型构造包含图片的请求。

但本文件第 215 行的条件检查 model.includes('gemma2'),使得普通 gemma 模型的图片被静默丢弃,用户不会收到任何提示或错误。

同时,packages/adapter-gemini/src/requester.ts 第 145 行仍按 'gemma' 关键字过滤模型列表,表明适配器预期支持普通 gemma 模型。

必须统一两处逻辑

  • 若 Gemini API 仅 gemma2 系列支持图片,则应在 shared-adapter 中将 'gemma' 改为 'gemma2'
  • gemma-7b 等普通 gemma 模型应支持图片,则需将第 215 行改回检查 'gemma'

两种方案都可行,但当前状态会导致静默数据丢失。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adapter-gemini/src/utils.ts` around lines 211 - 220, The model-image
support check in the condition using model.includes('gemma2') is inconsistent
with imageModelMatchers/supportImageInput which include 'gemma' and with
requester.ts which filters by 'gemma', causing gemma models' images to be
dropped; update the condition in the function containing this snippet to check
for 'gemma' as well (e.g., include model.includes('gemma') alongside
model.includes('gemma2') or replace 'gemma2' with 'gemma' to match the rest of
the adapter), keep the existing exclusion for 'gemini-1.0', and run relevant
tests or verify supportImageInput and requester.ts behaviors remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/middlewares/chat/stop_chat.ts`:
- Around line 84-87: The code is incorrectly re-checking request ownership by
calling ctx.chatluna.conversationRuntime.getRequestId(session, conversation.id),
which narrows stop permissions back to the original requester; instead remove
the session-based lookup and call stopConversationRequest with the conversation
id directly (use conversation.id as the identifier passed to
ctx.chatluna.conversationRuntime.stopConversationRequest), delete the requestId
variable usage, and keep the earlier permission: 'manage' semantics so
admins/shared managers can stop others' active requests.

In `@packages/core/src/services/conversation_runtime.ts`:
- Around line 449-478: dispose() aborts active requests then immediately removes
entries from activeByConversation, which leaves appendPendingMessage() Promises
waiting on roundDecisionResolvers unresolved; before deleting an active you must
either resolve its pending roundDecisionResolvers (resolve(false)) or call
completeRequest(active.conversationId, active.requestId) so the pending promise
can drain. Update dispose(platform?) to, for each matching active, first
clear/resolve the roundDecisionResolvers for that active (or invoke
completeRequest(active.conversationId, active.requestId)), then call
active.abortController.abort(...) and only after that delete from
activeByConversation and interfaces to avoid permanently hanging
appendPendingMessage() callers.

In `@packages/core/src/services/conversation.ts`:
- Around line 1828-1834: The current logic always defaults
constraint.users/constraint.excludeUsers to '[]' so an explicit null (meaning
"no restriction") is treated as an empty list and incorrectly blocks everyone;
update the checks so you first test whether constraint.users === null (in which
case skip the inclusion test), otherwise JSON.parse(constraint.users) into users
and then run the existing users != null && !users.includes(session.userId)
check; do the analogous change for constraint.excludeUsers (treat null as "no
excludes", only JSON.parse when not null) so the variables users and
excludeUsers reflect the intent of a null value rather than being coerced to [].

In `@packages/shared-adapter/src/client.ts`:
- Line 152: The supportImageInput list currently includes 'gemma' but
adapter-gemini's processImageParts only handles models containing 'gemma2',
causing images to be silently dropped; either remove 'gemma' from the
supportImageInput set or update processImageParts in
packages/adapter-gemini/src/utils.ts to also handle 'gemma' (or log a warning
when a model matches 'gemma' but not 'gemma2'); locate the supportImageInput
declaration in client.ts and the processImageParts function in adapter-gemini's
utils.ts and make the model-name checks consistent (or add a clear warning log
in processImageParts when an unsupported gemma variant is passed).
- Line 132: The fallback hardcoded return value in getModelMaxContextSize
(currently "return 200_000") increases the default context window for unmapped
models and can break token/counting logic downstream; change it to avoid a large
hardcoded value—either restore the previous fallback to use
getModelContextSize('o1-mini') or derive the fallback from
modelMaxContextSizeTable/configuration (or expose a configurable
DEFAULT_MODEL_MAX_CONTEXT_SIZE) so unknown models use the conservative prior
value; update getModelMaxContextSize and any callers that assume the smaller
default and add a short comment documenting the chosen fallback behavior.

---

Outside diff comments:
In `@packages/adapter-gemini/src/utils.ts`:
- Around line 211-220: The model-image support check in the condition using
model.includes('gemma2') is inconsistent with
imageModelMatchers/supportImageInput which include 'gemma' and with requester.ts
which filters by 'gemma', causing gemma models' images to be dropped; update the
condition in the function containing this snippet to check for 'gemma' as well
(e.g., include model.includes('gemma') alongside model.includes('gemma2') or
replace 'gemma2' with 'gemma' to match the rest of the adapter), keep the
existing exclusion for 'gemini-1.0', and run relevant tests or verify
supportImageInput and requester.ts behaviors remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b89fe3ae-b879-4efe-8a6a-d12be36626de

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc2c11 and 98c4c73.

📒 Files selected for processing (16)
  • packages/adapter-gemini/src/utils.ts
  • packages/core/src/llm-core/agent/legacy-executor.ts
  • packages/core/src/llm-core/agent/sub-agent.ts
  • packages/core/src/llm-core/agent/types.ts
  • packages/core/src/llm-core/platform/service.ts
  • packages/core/src/middlewares/chat/stop_chat.ts
  • packages/core/src/middlewares/conversation/request_conversation.ts
  • packages/core/src/services/chat.ts
  • packages/core/src/services/conversation.ts
  • packages/core/src/services/conversation_runtime.ts
  • packages/core/src/services/message_transform.ts
  • packages/core/src/services/types.ts
  • packages/core/tests/conversation-runtime.spec.ts
  • packages/extension-agent/src/service/permissions.ts
  • packages/extension-agent/src/service/sub_agent.ts
  • packages/shared-adapter/src/client.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/extension-agent/src/service/sub_agent.ts
  • packages/core/src/services/message_transform.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/llm-core/agent/sub-agent.ts
  • packages/core/src/llm-core/agent/types.ts
  • packages/core/src/llm-core/agent/legacy-executor.ts

@dingyi222666 dingyi222666 merged commit abf06bb into ChatLunaLab:v1-dev Apr 5, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants